package aufgabe2;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Observable;

import sun.security.acl.PermissionImpl;

public class SubscribeableImpl extends Observable implements Serializable, Subscribeable  {
    
    /**
     * Serialization
     */
    private static final long serialVersionUID = -2435616006556314047L;
    
    /** title of course */
    private String title;
    /** students can start to subscribe at this date */
    private GregorianCalendar subscribeStart;
    /** subscribing ends at this date */
    private GregorianCalendar subscribeEnd;
    /** students can't unsubscribe after this date */
    private GregorianCalendar unsubscribeEnd;
    /** list of subscribed students */
    private HashMap<Integer, Student> subscribedStudents;
    /** these courses need to be completed before subscribing to the course **/
    private ArrayList<Course> requirements;
    /** number of available seats, i.e. how many students can subscribe to this course **/
    private int maxSeats;
    /** the teachers that teach this course **/
    private HashMap<Integer, Teacher> teachers;
    /** unique identification **/
    private int id;
    
    public SubscribeableImpl(int id, String title, GregorianCalendar subscribeStart,
          GregorianCalendar subscribeEnd, GregorianCalendar unsubscribeEnd,
          ArrayList<Course> requirements, int maxSeats) {
        this.id = id;
        this.title = title;
        this.subscribeStart = subscribeStart;
        this.subscribeEnd = subscribeEnd;
        this.unsubscribeEnd = unsubscribeEnd;
        subscribedStudents = new HashMap<Integer, Student>();
        this.requirements = requirements;
        this.maxSeats = maxSeats;
        
        teachers = new HashMap<Integer, Teacher>();
    }
    
    /**
     * Subscribes a student to the course.
     *
     * @param student
     *            Student to be added to the course.
     * @param today
     *            The actual date.
     * @throws CourseException
     *             if no student is specified or he already is subscribed.
     */
    public void subscribeStudent(Student student, GregorianCalendar today)
            throws CourseException {
      if (student == null) {
        throw new CourseException("No Student set");
      }
      if (!subscribeStart.before(today) || !subscribeEnd.after(today)) {
        throw new CourseException(
                "Only can subscribe during subscription period");
      }
      if (!checkRequirements(student)) {
        throw new CourseException("Student doesn't match the requirements");
      }
      if (getMaxSeats() != 0 && subscribedStudents.size() >= getMaxSeats()) {
        throw new CourseException("Course is full.");
      }
      if (!subscribedStudents.containsKey(student.getMatrikelNr())) {
        subscribedStudents.put(student.getMatrikelNr(), student);
        this.addObserver(student);
      } else {
        throw new CourseException("Student is already subscribed");
      }
    }

    /**
     * Unsubscribes a student from the course.
     *
     * @param student
     *            Student who unsubscribes.
     * @param today
     *            The actual date.
     * @throws CourseException
     *             if no student is specified or if he isn't subscribed to the
     *             course.
     */
    public void unsubscribeStudent(Student student, GregorianCalendar today)
            throws CourseException {
      if (student == null) {
        throw new CourseException("No Student set");
      }
      if (!unsubscribeEnd.after(today)) {
        throw new CourseException(
                "Only can unsubscribe before end of unsubscriptions");
      }
      if (subscribedStudents.remove(student.getMatrikelNr()) == null) {
        throw new CourseException("Student is not subscribed");
      }
    }
    
    /**
     * Getter-method for title.
     *
     * @return the title of the course
     */
    public String getTitle() {
      return title;
    }
    
    /**
     * @param title the title to set
     */
    public void setTitle(String title) throws AccessDeniedException {
      checkUserCanEditCourse();
      this.title = title;
      notifyStudents(new StudentNotification("Course with Nr. "+ id +" changed the title to "+title));
    }
    
    /**
     * Shows how many students have subscribed to this couse.
     *
     * @return number of subscribed students
     */
    public int numberOfSubscribedStudents() {
      return subscribedStudents.size();
    }

    /**
     * lists all currently subscribed students of this course
     *
     * @return a String list of subscribed students
     */
    public String listStudents() {
      Iterator<Student> it = subscribedStudents.values().iterator();
      String list = new String();
      while (it.hasNext()) {
        Student s = it.next();
        list += s.toString();
      }
      return list;
    }

    /**
     * modifies the maximum number of students that can subscribe
     *
     * @param max
     * @throws CourseException
     */
    public void setMaxSeats(int max) throws CourseException, AccessDeniedException {
      checkUserCanEditCourse();
      if (numberOfSubscribedStudents() > max) {
        throw new CourseException("There are currently more students"
                + "subscribed than the maximum you want to specify");
      } else {
        this.maxSeats = max;
      }
    }

    /**
     * defines a properly formatted String for Course-objects
     *
     * @return formated representation of a Course-object
     */
    public String toString() {
      return id + "\t" + getTitle() + "\t\t"
              + getSubscribeStart().getTime().toString() + "\t"
              + getSubscribeEnd().getTime().toString() + "\t"
              + getUnsubscribeEnd().getTime().toString() + "\t"
              + numberOfSubscribedStudents();
    }

    /**
     * Check requirements for a student to subscribe to this course checks:
     * student cannot subscribe if he has successfully completed this course
     * already
     *
     * @param student
     * @return
     */
    private boolean checkRequirements(Student student) {
      if (getRequirements() == null) {
        return true;
      }
      Map<Course, Grade> completedCourses = student.getCompletedCourses();
      if (completedCourses == null) {
        return false;
      }
      for (Course course : getRequirements()) {
        Grade studGrade = completedCourses.get(course);
        if(studGrade == null) {
            return false;
        }
        if (!studGrade.hasPassed()) {
          return false;
        }
      }
      return true;
    }
    
    /**
     * @return the subscribeStart
     */
    public GregorianCalendar getSubscribeStart() {
      return subscribeStart;
    }

    /**
     * @param subscribeStart the subscribeStart to set
     */
    public void setSubscribeStart(GregorianCalendar subscribeStart) throws AccessDeniedException {
      checkUserCanEditCourse();
      this.subscribeStart = subscribeStart;
      notifyStudents(new StudentNotification("Course "+ title +" ("+id+") changed the subscription start to "+subscribeStart));
    }

    /**
     * @return the subscribeEnd
     */
    public GregorianCalendar getSubscribeEnd() {
      return subscribeEnd;
    }

    /**
     * @param subscribeEnd the subscribeEnd to set
     */
    public void setSubscribeEnd(GregorianCalendar subscribeEnd) throws AccessDeniedException {
      checkUserCanEditCourse();
      if (subscribeStart.after(subscribeEnd)) {
        throw new RuntimeException("End date before start date.");
      }
      this.subscribeEnd = subscribeEnd;
      notifyStudents(new StudentNotification("Course "+ title +" ("+id+") changed the subscription end to "+subscribeEnd));
    }
    
    /**
     * @return the unique identification
     */
    public int getId(){
        return id;
    }
    
    /**
     * @param the new unique id
     */
    public void setId(int id){
        this.id = id;
    }

    /**
     * @return the unsubscribeEnd
     */
    public GregorianCalendar getUnsubscribeEnd() {
      return unsubscribeEnd;
    }

    /**
     * @param unsubscribeEnd the unsubscribeEnd to set
     */
    public void setUnsubscribeEnd(GregorianCalendar unsubscribeEnd) throws AccessDeniedException {
      checkUserCanEditCourse();
      if (unsubscribeEnd.before(subscribeStart)) {
        throw new RuntimeException("UnsubscribeEnd date before startSubscribe date.");
      }
      notifyStudents(new StudentNotification("Course "+ title +" ("+id+") changed the unsubscription start to "+unsubscribeEnd));
      this.unsubscribeEnd = unsubscribeEnd;
    }

    /**
     * @return the requirements
     */
    public ArrayList<Course> getRequirements() {
      return requirements;
    }

    /**
     * @return the maxSeats
     */
    public int getMaxSeats() {
      return maxSeats;
    }

    /**
     * Checks user access to edit this course.
     * @return True if he is allowed, False if he is not.
     */
    protected void checkUserCanEditCourse() throws AccessDeniedException {
      AccessManager.getInstance().checkAccess(new PermissionImpl("courseEditSettings"));
    }
    
    /**
     * Notify students about changes in this course via observer pattern
     * @param notification the notification you want to give to the students
     */
    public void notifyStudents(StudentNotification notification) {
      this.setChanged();
      this.notifyObservers(notification);
    }

    /**
     * getter for the courses teachers
     * @return the teachers of the course
     */
    public HashMap<Integer, Teacher> getTeachers() {
      return teachers;
    }
    
    /**
     * adds a teacher to the course
     * @param teacher
     * @throws NoAccessException
     */
    public void addTeacher(Teacher teacher) throws AccessDeniedException {
      AccessManager.getInstance().checkAccess(new PermissionImpl("courseAddTeacher"));
      teachers.put(teacher.getId(), teacher);
      teacher.addCourse(this);
      addObserver(teacher);
      this.setChanged();
      Notification notification = new TeacherNotification("Added teacher " + teacher.toString() + " to course "
              + getTitle());

      this.notifyObservers(notification);
    }

}
